package gotpsess

import (
	"net"
	"net/http"
	"os"
	"sieve_admin_server_userpass/pkg/app"
	"sieve_admin_server_userpass/pkg/config"
	"sieve_admin_server_userpass/pkg/im3/app/api"
	"sieve_admin_server_userpass/pkg/log"
	"sieve_admin_server_userpass/pkg/net/http/gotpsess2"
	"sieve_admin_server_userpass/pkg/util"
	"strconv"
	"strings"
	"sync"
	"time"
)

var (
	fPath = *config.String("im.gotpsess.login.path", "/login", "")
	fTpl  = config.String("im.gotpsess.login.tpl", "", "")
	fUids = config.Ints("im.gotpsess.login.uid", []int{}, "")

	limiter = NewLimiter()
)

type User = gotpsess2.User

func init() {
	app.OnStart(func() {
		http.HandleFunc(fPath, login)
		loadUids()
		config.OnReload(loadUids)
	})
}

func loadUids() {
	m := map[string]string{}
	if uids := *fUids; len(uids) > 0 {
		ret, err := api.GOTPGetSecret(uids)
		if err != nil {
			log.Err(err)
			return
		}
		for k, v := range ret {
			m[strconv.Itoa(k)] = v
		}
	}
	gotpsess2.SetUserSecrets(m)
}

func login(w http.ResponseWriter, r *http.Request) {
	defer log.RecoverPanic()
	if r.Method == http.MethodPost {
		if err := r.ParseForm(); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		q := r.PostForm
		sUid := q.Get("uid")
		if uid, _ := strconv.Atoi(sUid); uid == 0 || !util.Contains(*fUids, uid) {
			w.WriteHeader(http.StatusForbidden)
			showLogin(w, r, "无权限")
			return
		}
		ip, _, _ := net.SplitHostPort(r.RemoteAddr)
		if limiter.Limited(sUid, ip) {
			w.WriteHeader(http.StatusForbidden)
			showLogin(w, r, "禁止频繁请求")
			return
		}
		if !gotpsess2.Login(w, r, sUid, q.Get("pwd")) {
			w.WriteHeader(http.StatusForbidden)
			showLogin(w, r, "动态密码错误")
			return
		}

		url := "/"
		if u := r.URL.Query().Get("url"); u != "" {
			url = u
		}
		http.Redirect(w, r, url, http.StatusFound)
	} else {
		showLogin(w, r, "")
	}
}

func showLogin(w http.ResponseWriter, r *http.Request, err string) {
	data := tplLogin
	if tpl := *fTpl; tpl != "" {
		if b, e := os.ReadFile(tpl); e == nil && len(b) > 0 {
			data = string(b)
		}
	}
	w.Write([]byte(strings.ReplaceAll(data, "{{ERR}}", err)))
}

func LoginRequired(handler func(http.ResponseWriter, *http.Request, *User)) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		defer log.RecoverPanic()
		user := gotpsess2.GetUser(w, r)
		if user == nil {
			http.Redirect(w, r, fPath+"?url="+r.RequestURI, http.StatusFound)
			return
		}
		handler(w, r, user)
	}
}

func Logout(w http.ResponseWriter, r *http.Request) {
	gotpsess2.Logout(w, r)
	http.Redirect(w, r, fPath, http.StatusFound)
}

var tplLogin = `<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
<style>
*{ margin: 0; padding: 0; font-family: "Microsoft YaHei","Heiti SC",Helvetica,Arial,PingFang SC,sans-serif; font-weight: normal; font-style: normal; }
body{ background: #f6f6f6; text-align: center; display: flex; align-items: center; justify-content: center; height: 100%;}
p{ margin: 20px; }
.input{ width: 260px; height: 50px; border-radius: 4px; font-size: 22px; }
.input{ border: 1px solid #d6d7dc; text-indent: 10%; color: #b3b3b3; outline: none; }
.input::-moz-placeholder,.input::-webkit-input-placeholder{ color: #b3b3b3; }
.submit{ width: 260px; height: 40px; border-radius: 4px; font-size: 22px; }
.submit{ display: block; border:none; margin: 18px auto; background: #00c3b3; color:#feffff; }
</style>
</head>
<body>
<form method="post" action="` + fPath + `">
<article>
    <p><input name="uid" type="number" placeholder="工号" class="input" required /></p>
    <p><input name="pwd" type="password" placeholder="动态密码" class="input" required /></p>
    <p><input type="submit" value="登录" class="submit" /></p>
</article>
<div style="height:400px">
    <span style="font-size: 20px; color: #ff374d">{{ERR}}</span>
</div>
</form>
</body>
</html>`

//

type Limiter struct {
	lock sync.Mutex
	data map[string]int64
}

func NewLimiter() *Limiter {
	l := &Limiter{data: map[string]int64{}}
	app.Async(l.clean)
	return l
}

func (l *Limiter) Limited(ks ...string) bool {
	l.lock.Lock()
	defer l.lock.Unlock()
	now := time.Now().Unix()
	for _, k := range ks {
		if t, ok := l.data[k]; ok && t == now {
			return true
		}
		l.data[k] = now
	}
	return false
}

func (l *Limiter) clean() {
	for {
		time.Sleep(time.Minute)
		l.doClean()
	}
}

func (l *Limiter) doClean() {
	l.lock.Lock()
	defer l.lock.Unlock()
	dels := []string{}
	et := time.Now().Unix() - 30
	for k, t := range l.data {
		if t < et {
			dels = append(dels, k)
		}
	}
	for _, k := range dels {
		delete(l.data, k)
	}
}
